/*
 * This file is part of Annuaire.
 *
 * Annuaire is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Annuaire is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Annuaire.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.miage.annuaire.data;

import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.miage.annuaire.Membre;
import org.miage.annuaire.Structure;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Cette classe permet d'accéder aux données de l'annuaire au format XML.
 * @author guhumber
 */
public class XmlDataAccessImpl implements XmlDataAccess {

	/**
	 * Renvoie l'annuaire au complet sous format XML.
	 */
	@Override
	public Document getAllStructures()
			throws ParserConfigurationException {
		
		List<Structure> structures =
			this.getObjectDataAccess().getAllStructures();
		
		// TODO: factoriser
		// Création du document XML.
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document document = builder.newDocument();
		
		Element annuaire = document.createElement("annuaire");
		
		for (Structure s : structures) {
			Element structureNode = document.createElement("structure");
			this.fillStructure(document, structureNode, s);
			annuaire.appendChild(structureNode);
		}
		
		document.appendChild(annuaire);
		
		return document;
	}
	
	/**
	 * Renvoie un document XML qui contient toutes les structures dont les clefs
	 * sont passées en paramètres. Les structures sont contenues linéairement
	 * et ne sont pas imbriquées.
	 */
	@Override
	public Document getStructuresByKeys(List<String> keys)
			throws ParserConfigurationException {
		
		// TODO: factoriser
		// Création du document XML.
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document document = builder.newDocument();
		
		Element annuaire = document.createElement("annuaire");
		
		for (String key : keys) {
			Structure structure =
					this.getObjectDataAccess().getStructureByKey(key);
			
			Element structureNode = document.createElement("structure");
			this.fillStructure(document, structureNode, structure);
			
			annuaire.appendChild(structureNode);
		}
		
		document.appendChild(annuaire);
		
		return document;
	}
	
	/**
	 * Remplit une structure XML récursivement.
	 * @param document Le document XML dans lequel les éléments sont crées.
	 * @param structureNode Le noeud structure à remplir.
	 * @param structure La structure en objet Java dans laquelle rechercher les
	 * informations.
	 */
	protected void fillStructure(Document document, Node structureNode,
			Structure structure) {
		
		this.appendTextChild(document, structureNode, "key",
				structure.getKey());
		
		this.appendTextChild(document, structureNode, "level",
				Integer.toString(structure.getLevel()));
		
		this.appendTextChild(document, structureNode, "short-name",
				structure.getShortName());
		
		this.appendTextChild(document, structureNode, "complete-name",
				structure.getCompleteName());
		
		if (structure.getLeader() != null) {
			Element leader = document.createElement("leader");
			this.appendTextChild(document, leader, "key",
					structure.getLeader().getKey());
			this.appendTextChild(document, leader, "name",
					structure.getLeader().getName());
			this.appendTextChild(document, leader, "phone",
					structure.getLeader().getPhone());
			this.appendTextChild(document, leader, "office",
					structure.getLeader().getOffice());
			this.appendTextChild(document, leader, "function",
					structure.getLeader().getFunction());
			this.appendTextChild(document, leader, "title",
					structure.getLeader().getTitre());
			structureNode.appendChild(leader);
		}
		
		if (structure.getContact() != null) {
			Element contact = document.createElement("contact");
			this.appendTextChild(document, contact, "key",
					structure.getContact().getKey());
			this.appendTextChild(document, contact, "name",
					structure.getContact().getName());
			this.appendTextChild(document, contact, "phone",
					structure.getContact().getPhone());
			this.appendTextChild(document, contact, "office",
					structure.getContact().getOffice());
			this.appendTextChild(document, contact, "function",
					structure.getContact().getFunction());
			structureNode.appendChild(contact);
		}
		
		for (Membre assistant : structure.getAssistantLeaders()) {
			Element membre = document.createElement("assistant-leader");
			this.appendTextChild(document, membre, "key", assistant.getKey());
			this.appendTextChild(document, membre, "name", assistant.getName());
			this.appendTextChild(document, membre, "phone", 
					assistant.getPhone());
			this.appendTextChild(document, membre, "office",
					assistant.getOffice());
			this.appendTextChild(document, membre, "function",
					assistant.getFunction());
			this.appendTextChild(document, membre, "title",
					assistant.getTitre());
			structureNode.appendChild(membre);
		}
		
		for (Membre membre : structure.getMembres()) {
			Element membreNode = document.createElement("membre");
			this.appendTextChild(document, membreNode, "key", membre.getKey());
			this.appendTextChild(document, membreNode, "name",
					membre.getName());
			this.appendTextChild(document, membreNode, "phone", 
					membre.getPhone());
			this.appendTextChild(document, membreNode, "office",
					membre.getOffice());
			this.appendTextChild(document, membreNode, "function",
					membre.getFunction());
			structureNode.appendChild(membreNode);
		}
		
		for (Structure child : structure.getChilds()) {
			Element structureChild = document.createElement("structure");
			this.fillStructure(document, structureChild, child);
			structureNode.appendChild(structureChild);
		}
	}

	/*
	 * protected Node extractStruct(Document document, String key) {
	 * 
	 * Node structure = document.createElement(key)
	 * 
	 * Element name = new Element("structureName");
	 * name.setText(this.data.getShortName(key)); struct.addContent(name);
	 * 
	 * Element lname = new Element("structureLongName");
	 * lname.setText(this.data.getCompleteName(key)); struct.addContent(lname);
	 * 
	 * return struct; }
	 */

	/**
	 * Effectue une recherche sur les trois critères à la fois.
	 */
	@Override
	public Document getMembres(String name, String function, String structure)
			throws ParserConfigurationException {
		List<Membre> listMembres = getObjectDataAccess().getMembres(name,
				function, structure);
		return convertMembresToDocument(listMembres);
	}
	
	/**
	 * Renvoie une liste de membres en recherchant par leur nom. La recherche ne
	 * tient pas compte de la casse. Si le nom est une chaîne vide ou null, tous
	 * les membres sont renvoyés. Exemple: soit deux membres "TOTO" et "TATA".
	 * Si on recherche "toT", on aura "TOTO". Si on recherche "t" ou "" ou null,
	 * on aura "TOTO" et "TATA".
	 * @param name Le nom (ou la partie du nom) du membre à rechercher.
	 * @return Une liste de membres au format XML.
	 * @throws ParserConfigurationException S'il y a eu une erreur dans la
	 * construction du document XML.
	 */
	@Override
	public Document getMembresByName(String name)
			throws ParserConfigurationException {
		List<Membre> listMembres = getObjectDataAccess().getMembresByName(name);
		return convertMembresToDocument(listMembres);
	}

   /**
	 * Renvoie une liste de membres en recherchant par leur nom. La recherche ne
	 * tient pas compte de la casse. Si le nom est une chaîne vide ou null, tous
	 * les membres sont renvoyés. Exemple: soit deux membres "TOTO" et "TATA".
	 * Si on recherche "toT", on aura "TOTO". Si on recherche "t" ou "" ou null,
	 * on aura "TOTO" et "TATA".
	 * @param name Le nom (ou la partie du nom) du membre à rechercher.
	 * @return Une liste de membres au format XML.
	 * @throws ParserConfigurationException S'il y a eu une erreur dans la
	 * construction du document XML.
	 */
	@Override
	public Document getMembresByStructure(String structure)
			throws ParserConfigurationException {
		List<Membre> listMembres =
			getObjectDataAccess().getMembresByStructure(structure);
		return convertMembresToDocument(listMembres);
	}

   /**
	 * Renvoie une liste de membres en recherchant par leur fonction. La recherche ne
	 * tient pas compte de la casse. Si le nom est une chaîne vide ou null, tous
	 * les membres sont renvoyés. Exemple: soit deux membres "TOTO" et "TATA".
	 * Si on recherche "toT", on aura "TOTO". Si on recherche "t" ou "" ou null,
	 * on aura "TOTO" et "TATA".
	 * @param name Le nom (ou la partie du nom) du membre à rechercher.
	 * @return Une liste de membres au format XML.
	 * @throws ParserConfigurationException S'il y a eu une erreur dans la
	 * construction du document XML.
	 */
	@Override
	public Document getMembresByFunction(String function)
			throws ParserConfigurationException {
		List<Membre> listMembres = 
			getObjectDataAccess().getMembresByFunction(function);
		return convertMembresToDocument(listMembres);
	}

       @Override
	public List<Membre> getMembresByFunctionList(String function)
			throws ParserConfigurationException {
		List<Membre> listMembres =
			getObjectDataAccess().getMembresByFunction(function);
		return listMembres;
	}

        @Override
	public Document getMembresByNameWithFonction(String name,List<Membre> LaList)
			throws ParserConfigurationException {
		List<Membre> listMembres =
			getObjectDataAccess().getMembresByNameWithFonction(name,LaList);
		return convertMembresToDocument(listMembres);
	}
        
        @Override
	public List<Membre> getMembresByNameWithFonctionList(String name,List<Membre> LaList)
			throws ParserConfigurationException {
		List<Membre> listMembres =
			getObjectDataAccess().getMembresByNameWithFonction(name,LaList);
		return listMembres;
	}

        @Override
	public List<Membre> getMembresByStructureList(String structure)
			throws ParserConfigurationException {
		List<Membre> listMembres =
			getObjectDataAccess().getMembresByStructure(structure);
		return listMembres;
	}

	/**
	 * Transforme une liste de membres en un document XML.
	 * @param listMembres Une liste de membres.
	 * @return Un document XML qui correspond à cette liste de membres.
	 * @throws ParserConfigurationException S'il y a eu une erreur dans la
	 * construction du document XML.
	 */
	protected Document convertMembresToDocument(List<Membre> listMembres)
			throws ParserConfigurationException {
		// Création du document XML.
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document document = builder.newDocument();

		// On crée l'élément racine 'membres'.
		Element elemMembres = document.createElement("membres");
		document.appendChild(elemMembres);

		// Pour chaque membre de la liste, on l'ajoute au document XML.
		for (Membre unMembre : listMembres) {
			// On crée un élément 'membre' (pour un seul membre).
			Element elemMembre = document.createElement("membre");

			// On lui ajoute des balises qui correspondent aux attributs de la
			// classe Membre.
			appendTextChild(document, elemMembre, "key", unMembre.getKey());
			appendTextChild(document, elemMembre, "name", unMembre.getName());
			appendTextChild(document, elemMembre, "phone", unMembre.getPhone());
			appendTextChild(document, elemMembre, "office",
					unMembre.getOffice());
			appendTextChild(document, elemMembre, "function",
					unMembre.getFunction());
			
			if (unMembre.getTitre() != null) {
				appendTextChild(document, elemMembre, "title",
						unMembre.getTitre());
			}
			
			appendTextChild(document, elemMembre, "leader",
					unMembre.isLeader() ? "true" : "false");
			appendTextChild(document, elemMembre, "contact",
					unMembre.isContact() ? "true" : "false");
			appendTextChild(document, elemMembre, "assistant",
					unMembre.isAssistantLeader() ? "true" : "false");

			// On fait de même pour la structure du membre.
			if (unMembre.getStructure() != null) {
				Element elemStructure = document.createElement("structure");

				appendTextChild(document, elemStructure, "key", unMembre
						.getStructure().getKey());

				appendTextChild(document, elemStructure, "short-name", unMembre
						.getStructure().getShortName());

				appendTextChild(document, elemStructure, "complete-name",
						unMembre.getStructure().getCompleteName());

				// La structure est insérée à l'intérieure de 'membre'.
				// On aura <membre>... <structure>...</structure></membre>
				elemMembre.appendChild(elemStructure);
			}

			// Enfin on ajoute le membre en tant que fils de l'élément racine
			// 'membres'.
			elemMembres.appendChild(elemMembre);
		}

		// Fin.
		return document;
	}

	/**
	 * Ajoute, dans un document XML, une balise fille à un élément. Cette balise
	 * fille contient du texte.
	 * @param document Un document XML.
	 * @param node Le noeud auquel on veut ajouter la balise fille.
	 * @param name Le nom de la balise fille.
	 * @param value Le texte de la balise fille.
	 */
	protected void appendTextChild(Document document, Node node, String name,
			String value) {
		Element element = document.createElement(name);
		element.appendChild(document.createTextNode(value));
		node.appendChild(element);
	}
	
	/**
	 * Accesseur en lecture sur l'attribut 'objectDataAccess'.
	 * @return L'interface qui nous permet de récupérer les données de
	 * l'annuaire sous forme d'objets Java.
	 */
	public ObjectDataAccess getObjectDataAccess() {
		return DataAccessFactory.getInstance().getObjectDataAccess();
	}
}
